﻿using MultiLang;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using BLToolkit.Data.Linq;
using JetBrains.Annotations;
using Rsdn.Janus.DataModel;
using Rsdn.SmartApp;

namespace Rsdn.Janus
{
    [Service(typeof(IMessageMarkService))]
    internal class MessageMarkService : IMessageMarkService, IDisposable
    {
        private readonly IServiceProvider _provider;
        private readonly AsyncOperation _uiAsyncOperation;
        private readonly ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim();
        private readonly AutoResetEvent _syncEvent = new AutoResetEvent(false);
        private readonly List<MarkRequest> _requests = new List<MarkRequest>();
        private bool _threadCreated;

        /// <summary>
        /// Initializes a new instance of the <see cref="MessageMarkService"/> class.
        /// </summary>
        /// <param name="provider">The provider.</param>
        /// <exception cref="ArgumentNullException"><paramref name="provider"/> is <c>null</c>.</exception>
        public MessageMarkService([NotNull] IServiceProvider provider)
        {
            if (provider == null)
            {
                throw new ArgumentNullException("provider");          //MLHIDE
            }

            _provider = provider;
            _uiAsyncOperation = _provider.GetRequiredService<IUIShell>().CreateUIAsyncOperation();
        }

        #region Implementation of IDisposable

        public void Dispose()
        {
            _uiAsyncOperation.OperationCompleted();
        }

        #endregion

        #region Implementation of IMessageMarkService

        /// <summary>
        /// Queues the message mark.
        /// </summary>
        /// <param name="msgIds">The MSG ids.</param>
        /// <param name="isRead">if set to <c>true</c> [is read].</param>
        /// <param name="markFinished">The mark finished.</param>
        /// <exception cref="ArgumentNullException"><paramref name="msgIds"/> is <c>null</c>.</exception>
        public void QueueMessageMark([NotNull] IEnumerable<ForumEntryIds> msgIds, bool isRead, Action markFinished)
        {
            if (msgIds == null)
            {
                throw new ArgumentNullException("msgIds");            //MLHIDE
            }

            ForumEntryIds[] msgIdsArray = msgIds.ToArray();
            using (_rwLock.GetWriterLock())
            {
                AsyncOperation asyncOp = null;
                if (markFinished != null)
                {
                    asyncOp = AsyncHelper.CreateOperation();
                }

                Action finishedAction =
                    delegate
                        {
                            if (markFinished != null)
                            {
                                // ReSharper disable AssignNullToNotNullAttribute
                                asyncOp.PostOperationCompleted(markFinished);
                                // ReSharper restore AssignNullToNotNullAttribute
                            }
                        };

                MarkRequest markRequest = new MarkRequest(msgIdsArray, isRead, finishedAction);

                _requests.Add(markRequest);
                _syncEvent.Set();
                EnsureThread();
            }
        }

        #endregion


        #region Private Members

        private void EnsureThread()
        {
            if (_threadCreated)
            {
                return;
            }
            _threadCreated = true;

            Thread thread = new Thread(WorkProc);
            thread.IsBackground = true;
            thread.Start();
        }

        /// <summary>
        /// MarkMessages thread function.
        /// </summary>
        private void WorkProc()
        {
            while (true)
            {
                _syncEvent.WaitOne();
                try
                {
                    MarkMessages();
                }
                catch (Exception ex)
                {
                    // Note: will it works from thread?
                    _uiAsyncOperation.Post(delegate { throw ex; });
                }
            }
            // ReSharper disable FunctionNeverReturns
        }
        // ReSharper restore FunctionNeverReturns

        /// <summary>
        /// Marks the MSGS read.
        /// </summary>
        /// <param name="provider">The provider.</param>
        /// <param name="msgIds">The MSG ids.</param>
        /// <param name="isRead">if set to <c>true</c> [is read].</param>
        /// <exception cref="ArgumentException">Элемент последовательности не является сообщением.</exception>
        private static void MarkMsgsRead(
            IServiceProvider provider,
            IEnumerable<ForumEntryIds> msgIds,
            bool isRead)
        {
            var msgIdsArray = msgIds.ToArray();
            if (!msgIdsArray.All(ids => ids.GetEntryType() == ForumEntryType.Message))
            {
                throw new ArgumentException(ml.ml_string(357, @"Элемент последовательности не является сообщением."), 
                    "msgIds");                                        //MLHIDE
            }

			using (var db = provider.CreateDBContext())
            {
			using (var tx = db.BeginTransaction())
                {
                    IEnumerable<int?> enumerable = msgIds.Select(ids => ids.MessageId);

                    IEnumerable<IEnumerable<int?>> splitForInClause = enumerable.SplitForInClause(provider);

                    foreach (IEnumerable<int?> series in splitForInClause)
                    {
                        IEnumerable<int?> locSeries = series;
                        IQueryable<IForumMessage> forumMessages = db.Messages(m => locSeries.Contains(m.ID));

                        forumMessages
                            .Set(_ => _.IsRead, isRead)
                            .Update();
                    }

                    IEnumerable<int> ints = msgIdsArray.Select(ids => ids.TopicId.GetValueOrDefault());

                    DatabaseManager.UpdateTopicInfoRange(provider, db, ints.Distinct(), Config.Instance.SelfId);

                    tx.Commit();
                }
				
            }
        }

        /// <exception cref="System.ArgumentNullException">source or selector is null.</exception>
        private void MarkMessages()
        {
            HashSet<MarkRequest> hash;

            using (_rwLock.GetReaderLock())
            {
                hash = new HashSet<MarkRequest>(_requests);
            }

            IEnumerable<IGrouping<bool, MarkRequest>> groupBy = hash.GroupBy(rq => rq.IsRead);

            var groups = groupBy.Select(
                delegate(IGrouping<bool, MarkRequest> grp)
                    {
                        // temp in debugger --> 
                        //Ids = {System.Linq.Enumerable.SelectManyIterator<Rsdn.Janus.MessageMarkService.MarkRequest,Rsdn.Janus.ForumEntryIds>}
                        // returned anonymous class
                        var temp = new {IsRead = grp.Key, Ids = grp.SelectMany(rq => rq.MsgIds)};
                        return temp;
                    }
                );

            foreach (var group in groups)
            {
                MarkMsgsRead(_provider, group.Ids, group.IsRead);
            }

            using (_rwLock.GetWriterLock())
            {
                _requests.RemoveAll(hash.Contains);
            }

            IEnumerable<Action> notificators = hash.Select(rq => rq.MarkFinished);
            foreach (Action notificator in notificators)
            {
                notificator();
            }
        }

        #endregion

        #region MarkRequest class
        private class MarkRequest
        {
            private readonly ForumEntryIds[] _msgIds;
            private readonly bool _isRead;
            private readonly Action _markFinished;

            public MarkRequest(ForumEntryIds[] msgIds, bool isRead, Action markFinished)
            {
                _msgIds = msgIds;
                _isRead = isRead;
                _markFinished = markFinished;
            }

            public ForumEntryIds[] MsgIds
            {
                get { return _msgIds; }
            }

            public bool IsRead
            {
                get { return _isRead; }
            }

            public Action MarkFinished
            {
                get { return _markFinished; }
            }
        }
        #endregion
    }
}